package scalab.core.utils

import scala.collection.immutable.{SortedSet, TreeSet, Map, HashSet}
import scalab.core.component.{Component, Universe, SinkComponent}
import scalab.core.gates.Input
import scalab.utils.{Hexadecimal, MD5, Digest}

final class IncidenceMatrix(
	components: SortedSet[Component], 
	inputIndexes: Map[Input, Int], 
	componentsDeps: Map[Component, Set[Component]]){
	
	//val name = "matrix"

	val nbRows = components.size
	val nbCols = inputIndexes.size + 1 //for the universe...
	val matrix: Array[Array[Int]] = new Array[Array[Int]](nbRows, nbCols)

	def apply(i: Int, j: Int): Int = row(i)(j)
	def update(i: Int, j: Int, x: Int): Unit =
		matrix.update(i, {row(i).update(j, x); row(i)})

	def +=(i: Int, j: Int, offset: Int): Unit = 
		update(i, j, this(i, j) + offset)

	/*private var identifier: String = _
	def id: String = identifier*/
	//init
	private def init{
		//info("nbRows: " + nbRows)
		//info("nbCols: " + nbCols)
		for(i <- List.range(0, nbRows)){
//			info("row: " + i)
			val r: Array[Int] = new Array(nbCols)
			for(j <- List.range(0, nbCols)){
				//info("matrix(" + i + ")(" + j + ")")
				r.update(j, 0)
			}
			matrix.update(i, r)
		}
		//info("finished init")
		//info("components: " + (components map {c => c.name}).mkString(","))
	}
	init

	private def fill{
		components foreach {c => 
			c match{
				case s: SinkComponent =>
					s.inputs foreach {i =>
						i.providers foreach {p =>
							//info("p.index: " + p.index)
							val inputId = inputIndexes.get(i)
							//info("p.index: " + p.index + " inputId: " +inputId.get)
							update(p.index, inputId.get, 1) //p -> c
						}
					}
				case _ => //Universe
			}
		}
		//info("finished fill")
	}
	fill

	/*private def computeId{
		val md = MD5.getMessageDigest
		components foreach{c =>
			Digest.update(md, c.behaviorId + row(c.index).mkString(":", "|", ""))
		}
		identifier = Hexadecimal.fromByteArray(md.digest)
	}
	computeId*/
	def getDependentCols(cDeps: SortedSet[Component]): List[Array[Int]] = {
		
		var cols: List[Array[Int]] = Nil
		cDeps foreach {c =>
			if(c ne Universe){
				c.asInstanceOf[SinkComponent].inputs foreach {i =>
					//this should not be possible to have a map miss
					cols = column(inputIndexes.getOrElse(i, 0)) :: cols
				}
			}
			else
				cols = column(c.index) :: cols
		}
		cols.reverse
	}

	def encodeCols(allComps: SortedSet[Component], cols: Iterable[Array[Int]]): String = {
		val comps = allComps.toList
		val sb: StringBuilder = new StringBuilder
		cols foreach {r =>
			var rowId = 0
			r foreach {e =>
				if(e == 1){
					sb append encodeNature(comps(rowId))
					sb append '$'
				}
				rowId += 1
			}
		}
		sb.toString
	}

	def encodeComponent(component: Component, allComps: SortedSet[Component]): String = {
		val cDeps = componentsDeps.getOrElse(component, HashSet.empty[Component])
		val sorted: SortedSet[Component] = 
			Component.orderComponents(cDeps)//index ordered
		val sb: StringBuilder = new StringBuilder
		sb append encodeCols(allComps, getDependentCols(sorted))
		val md = MD5.getMessageDigest
		Digest.update(md, sb.toString)
		Digest.getHash(md)//Hexadecimal.fromByteArray(md.digest(sb.toString.getBytes))
	}

	def encodeNature(c: Component): String = c.className + "#" + c.instanceId

	def row(i: Int): Array[Int] = matrix(i)
	def column(j: Int): Array[Int] = {
		val col = new Array[Int](nbRows)
		for(rowId <- List.range(0, nbRows)){
			col.update(rowId, this(rowId, j))
		}
		col
	}
}
